home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / lib / tcl / dist / tclGlob.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-07-01  |  14.7 KB  |  564 lines

  1. /* 
  2.  * tclGlob.c --
  3.  *
  4.  *    This file provides procedures and commands for file name
  5.  *    manipulation, such as tilde expansion and globbing.
  6.  *
  7.  * Copyright 1990-1991 Regents of the University of California
  8.  * Permission to use, copy, modify, and distribute this
  9.  * software and its documentation for any purpose and without
  10.  * fee is hereby granted, provided that the above copyright
  11.  * notice appear in all copies.  The University of California
  12.  * makes no representations about the suitability of this
  13.  * software for any purpose.  It is provided "as is" without
  14.  * express or implied warranty.
  15.  */
  16.  
  17. #ifndef lint
  18. static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclGlob.c,v 1.24 92/07/01 08:51:50 ouster Exp $ SPRITE (Berkeley)";
  19. #endif /* not lint */
  20.  
  21. #include "tclInt.h"
  22. #include "tclUnix.h"
  23.  
  24. /*
  25.  * The structure below is used to keep track of a globbing result
  26.  * being built up (i.e. a partial list of file names).  The list
  27.  * grows dynamically to be as big as needed.
  28.  */
  29.  
  30. typedef struct {
  31.     char *result;        /* Pointer to result area. */
  32.     int totalSpace;        /* Total number of characters allocated
  33.                  * for result. */
  34.     int spaceUsed;        /* Number of characters currently in use
  35.                  * to hold the partial result (not including
  36.                  * the terminating NULL). */
  37.     int dynamic;        /* 0 means result is static space, 1 means
  38.                  * it's dynamic. */
  39. } GlobResult;
  40.  
  41. /*
  42.  * Declarations for procedures local to this file:
  43.  */
  44.  
  45. static void        AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
  46.                 char *dir, char *separator, char *name,
  47.                 int nameLength));
  48. static int        DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
  49.                 char *rem));
  50.  
  51. /*
  52.  *----------------------------------------------------------------------
  53.  *
  54.  * AppendResult --
  55.  *
  56.  *    Given two parts of a file name (directory and element within
  57.  *    directory), concatenate the two together and append them to
  58.  *    the result building up in interp.
  59.  *
  60.  * Results:
  61.  *    There is no return value.
  62.  *
  63.  * Side effects:
  64.  *    Interp->result gets extended.
  65.  *
  66.  *----------------------------------------------------------------------
  67.  */
  68.  
  69. static void
  70. AppendResult(interp, dir, separator, name, nameLength)
  71.     Tcl_Interp *interp;        /* Interpreter whose result should be
  72.                  * appended to. */
  73.     char *dir;            /* Name of directory, without trailing
  74.                  * slash except for root directory. */
  75.     char *separator;        /* Separator string so use between dir and
  76.                  * name:  either "/" or "" depending on dir. */
  77.     char *name;            /* Name of file withing directory (NOT
  78.                  * necessarily null-terminated!). */
  79.     int nameLength;        /* Number of characters in name. */
  80. {
  81.     int dirFlags, nameFlags;
  82.     char *p, saved;
  83.  
  84.     /*
  85.      * Next, see if we can put together a valid list element from dir
  86.      * and name by calling Tcl_AppendResult.
  87.      */
  88.  
  89.     if (*dir == 0) {
  90.     dirFlags = 0;
  91.     } else {
  92.     Tcl_ScanElement(dir, &dirFlags);
  93.     }
  94.     saved = name[nameLength];
  95.     name[nameLength] = 0;
  96.     Tcl_ScanElement(name, &nameFlags);
  97.     if ((dirFlags == 0) && (nameFlags == 0)) {
  98.     if (*interp->result != 0) {
  99.         Tcl_AppendResult(interp, " ", dir, separator, name, (char *) NULL);
  100.     } else {
  101.         Tcl_AppendResult(interp, dir, separator, name, (char *) NULL);
  102.     }
  103.     name[nameLength] = saved;
  104.     return;
  105.     }
  106.  
  107.     /*
  108.      * This name has weird characters in it, so we have to convert it to
  109.      * a list element.  To do that, we have to merge the characters
  110.      * into a single name.  To do that, malloc a buffer to hold everything.
  111.      */
  112.  
  113.     p = (char *) ckalloc((unsigned) (strlen(dir) + strlen(separator)
  114.         + nameLength + 1));
  115.     sprintf(p, "%s%s%s", dir, separator, name);
  116.     name[nameLength] = saved;
  117.     Tcl_AppendElement(interp, p, 0);
  118.     ckfree(p);
  119. }
  120.  
  121. /*
  122.  *----------------------------------------------------------------------
  123.  *
  124.  * DoGlob --
  125.  *
  126.  *    This recursive procedure forms the heart of the globbing
  127.  *    code.  It performs a depth-first traversal of the tree
  128.  *    given by the path name to be globbed.
  129.  *
  130.  * Results:
  131.  *    The return value is a standard Tcl result indicating whether
  132.  *    an error occurred in globbing.  After a normal return the
  133.  *    result in interp will be set to hold all of the file names
  134.  *    given by the dir and rem arguments.  After an error the
  135.  *    result in interp will hold an error message.
  136.  *
  137.  * Side effects:
  138.  *    None.
  139.  *
  140.  *----------------------------------------------------------------------
  141.  */
  142.  
  143. static int
  144. DoGlob(interp, dir, rem)
  145.     Tcl_Interp *interp;            /* Interpreter to use for error
  146.                      * reporting (e.g. unmatched brace). */
  147.     char *dir;                /* Name of a directory at which to
  148.                      * start glob expansion.  This name
  149.                      * is fixed: it doesn't contain any
  150.                      * globbing chars. */
  151.     char *rem;                /* Path to glob-expand. */
  152. {
  153.     /*
  154.      * When this procedure is entered, the name to be globbed may
  155.      * already have been partly expanded by ancestor invocations of
  156.      * DoGlob.  The part that's already been expanded is in "dir"
  157.      * (this may initially be empty), and the part still to expand
  158.      * is in "rem".  This procedure expands "rem" one level, making
  159.      * recursive calls to itself if there's still more stuff left
  160.      * in the remainder.
  161.      */
  162.  
  163.     register char *p;
  164.     register char c;
  165.     char *openBrace, *closeBrace;
  166.     int gotSpecial, result;
  167.     char *separator;
  168.  
  169.     /*
  170.      * Figure out whether we'll need to add a slash between the directory
  171.      * name and file names within the directory when concatenating them
  172.      * together.
  173.      */
  174.  
  175.     if ((dir[0] == 0) || ((dir[0] == '/') && (dir[1] == 0))) {
  176.     separator = "";
  177.     } else {
  178.     separator = "/";
  179.     }
  180.  
  181.     /*
  182.      * When generating information for the next lower call,
  183.      * use static areas if the name is short, and malloc if the name
  184.      * is longer.
  185.      */
  186.  
  187. #define STATIC_SIZE 200
  188.  
  189.     /*
  190.      * First, find the end of the next element in rem, checking
  191.      * along the way for special globbing characters.
  192.      */
  193.  
  194.     gotSpecial = 0;
  195.     openBrace = closeBrace = NULL;
  196.     for (p = rem; ; p++) {
  197.     c = *p;
  198.     if ((c == '\0') || (c == '/')) {
  199.         break;
  200.     }
  201.     if ((c == '{') && (openBrace == NULL)) {
  202.         openBrace = p;
  203.     }
  204.     if ((c == '}') && (closeBrace == NULL)) {
  205.         closeBrace = p;
  206.     }
  207.     if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
  208.         gotSpecial = 1;
  209.     }
  210.     }
  211.  
  212.     /*
  213.      * If there is an open brace in the argument, then make a recursive
  214.      * call for each element between the braces.  In this case, the
  215.      * recursive call to DoGlob uses the same "dir" that we got.
  216.      * If there are several brace-pairs in a single name, we just handle
  217.      * one here, and the others will be handled in recursive calls.
  218.      */
  219.  
  220.     if (openBrace != NULL) {
  221.     int remLength, l1, l2;
  222.     char static1[STATIC_SIZE];
  223.     char *element, *newRem;
  224.  
  225.     if (closeBrace == NULL) {
  226.         Tcl_ResetResult(interp);
  227.         interp->result = "unmatched open-brace in file name";
  228.         return TCL_ERROR;
  229.     }
  230.     remLength = strlen(rem) + 1;
  231.     if (remLength <= STATIC_SIZE) {
  232.         newRem = static1;
  233.     } else {
  234.         newRem = (char *) ckalloc((unsigned) remLength);
  235.     }
  236.     l1 = openBrace-rem;
  237.     strncpy(newRem, rem, l1);
  238.     p = openBrace;
  239.     for (p = openBrace; *p != '}'; ) {
  240.         element = p+1;
  241.         for (p = element; ((*p != '}') && (*p != ',')); p++) {
  242.         /* Empty loop body:  just find end of this element. */
  243.         }
  244.         l2 = p - element;
  245.         strncpy(newRem+l1, element, l2);
  246.         strcpy(newRem+l1+l2, closeBrace+1);
  247.         if (DoGlob(interp, dir, newRem) != TCL_OK) {
  248.         return TCL_ERROR;
  249.         }
  250.     }
  251.     if (remLength > STATIC_SIZE) {
  252.         ckfree(newRem);
  253.     }
  254.     return TCL_OK;
  255.     }
  256.  
  257.     /*
  258.      * If there were any pattern-matching characters, then scan through
  259.      * the directory to find all the matching names.
  260.      */
  261.  
  262.     if (gotSpecial) {
  263.     DIR *d;
  264.     struct dirent *entryPtr;
  265.     int l1, l2;
  266.     char *pattern, *newDir, *dirName;
  267.     char static1[STATIC_SIZE], static2[STATIC_SIZE];
  268.     struct stat statBuf;
  269.  
  270.     /*
  271.      * Be careful not to do any actual file system operations on a
  272.      * directory named "";  instead, use ".".  This is needed because
  273.      * some versions of UNIX don't treat "" like "." automatically.
  274.      */
  275.  
  276.     if (*dir == '\0') {
  277.         dirName = ".";
  278.     } else {
  279.         dirName = dir;
  280.     }
  281.     if ((stat(dirName, &statBuf) != 0)
  282.         || ((statBuf.st_mode & S_IFMT) != S_IFDIR)) {
  283.         return TCL_OK;
  284.     }
  285.     d = opendir(dirName);
  286.     if (d == NULL) {
  287.         Tcl_ResetResult(interp);
  288.         Tcl_AppendResult(interp, "couldn't read directory \"",
  289.             dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
  290.         return TCL_ERROR;
  291.     }
  292.     l1 = strlen(dir);
  293.     l2 = (p - rem);
  294.     if (l2 < STATIC_SIZE) {
  295.         pattern = static2;
  296.     } else {
  297.         pattern = (char *) ckalloc((unsigned) (l2+1));
  298.     }
  299.     strncpy(pattern, rem, l2);
  300.     pattern[l2] = '\0';
  301.     result = TCL_OK;
  302.     while (1) {
  303.         entryPtr = readdir(d);
  304.         if (entryPtr == NULL) {
  305.         break;
  306.         }
  307.  
  308.         /*
  309.          * Don't match names starting with "." unless the "." is
  310.          * present in the pattern.
  311.          */
  312.  
  313.         if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
  314.         continue;
  315.         }
  316.         if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
  317.         int nameLength = strlen(entryPtr->d_name);
  318.         if (*p == 0) {
  319.             AppendResult(interp, dir, separator, entryPtr->d_name,
  320.                 nameLength);
  321.         } else {
  322.             if ((l1+nameLength+2) <= STATIC_SIZE) {
  323.             newDir = static1;
  324.             } else {
  325.             newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
  326.             }
  327.             sprintf(newDir, "%s%s%s", dir, separator, entryPtr->d_name);
  328.             result = DoGlob(interp, newDir, p+1);
  329.             if (newDir != static1) {
  330.             ckfree(newDir);
  331.             }
  332.             if (result != TCL_OK) {
  333.             break;
  334.             }
  335.         }
  336.         }
  337.     }
  338.     closedir(d);
  339.     if (pattern != static2) {
  340.         ckfree(pattern);
  341.     }
  342.     return result;
  343.     }
  344.  
  345.     /*
  346.      * This is the simplest case:  just another path element.  Move
  347.      * it to the dir side and recurse (or just add the name to the
  348.      * list, if we're at the end of the path).
  349.      */
  350.  
  351.     if (*p == 0) {
  352.     AppendResult(interp, dir, separator, rem, p-rem);
  353.     } else {
  354.     int l1, l2;
  355.     char *newDir;
  356.     char static1[STATIC_SIZE];
  357.  
  358.     l1 = strlen(dir);
  359.     l2 = l1 + (p - rem) + 2;
  360.     if (l2 <= STATIC_SIZE) {
  361.         newDir = static1;
  362.     } else {
  363.         newDir = (char *) ckalloc((unsigned) l2);
  364.     }
  365.     sprintf(newDir, "%s%s%.*s", dir, separator, p-rem, rem);
  366.     result = DoGlob(interp, newDir, p+1);
  367.     if (newDir != static1) {
  368.         ckfree(newDir);
  369.     }
  370.     if (result != TCL_OK) {
  371.         return TCL_ERROR;
  372.     }
  373.     }
  374.     return TCL_OK;
  375. }
  376.  
  377. /*
  378.  *----------------------------------------------------------------------
  379.  *
  380.  * Tcl_TildeSubst --
  381.  *
  382.  *    Given a name starting with a tilde, produce a name where
  383.  *    the tilde and following characters have been replaced by
  384.  *    the home directory location for the named user.
  385.  *
  386.  * Results:
  387.  *    The result is a pointer to a static string containing
  388.  *    the new name.  This name will only persist until the next
  389.  *    call to Tcl_TildeSubst;  save it if you care about it for
  390.  *    the long term.  If there was an error in processing the
  391.  *    tilde, then an error message is left in interp->result
  392.  *    and the return value is NULL.
  393.  *
  394.  * Side effects:
  395.  *    None that the caller needs to worry about.
  396.  *
  397.  *----------------------------------------------------------------------
  398.  */
  399.  
  400. char *
  401. Tcl_TildeSubst(interp, name)
  402.     Tcl_Interp *interp;        /* Interpreter in which to store error
  403.                  * message (if necessary). */
  404.     char *name;            /* File name, which may begin with "~/"
  405.                  * (to indicate current user's home directory)
  406.                  * or "~<user>/" (to indicate any user's
  407.                  * home directory). */
  408. {
  409. #define STATIC_BUF_SIZE 50
  410.     static char staticBuf[STATIC_BUF_SIZE];
  411.     static int curSize = STATIC_BUF_SIZE;
  412.     static char *curBuf = staticBuf;
  413.     char *dir;
  414.     int length;
  415.     int fromPw = 0;
  416.     register char *p;
  417.  
  418.     if (name[0] != '~') {
  419.     return name;
  420.     }
  421.  
  422.     /*
  423.      * First, find the directory name corresponding to the tilde entry.
  424.      */
  425.  
  426.     if ((name[1] == '/') || (name[1] == '\0')) {
  427.     dir = getenv("HOME");
  428.     if (dir == NULL) {
  429.         Tcl_ResetResult(interp);
  430.         Tcl_AppendResult(interp, "couldn't find HOME environment ",
  431.             "variable to expand \"", name, "\"", (char *) NULL);
  432.         return NULL;
  433.     }
  434.     p = name+1;
  435.     } else {
  436.     struct passwd *pwPtr;
  437.  
  438.     for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
  439.         /* Null body;  just find end of name. */
  440.     }
  441.     length = p-&name[1];
  442.     if (length >= curSize) {
  443.         length = curSize-1;
  444.     }
  445.     memcpy((VOID *) curBuf, (VOID *) (name+1), length);
  446.     curBuf[length] = '\0';
  447.     pwPtr = getpwnam(curBuf);
  448.     if (pwPtr == NULL) {
  449.         Tcl_ResetResult(interp);
  450.         Tcl_AppendResult(interp, "user \"", curBuf,
  451.             "\" doesn't exist", (char *) NULL);
  452.         return NULL;
  453.     }
  454.     dir = pwPtr->pw_dir;
  455.     fromPw = 1;
  456.     }
  457.  
  458.     /*
  459.      * Grow the buffer if necessary to make enough space for the
  460.      * full file name.
  461.      */
  462.  
  463.     length = strlen(dir) + strlen(p);
  464.     if (length >= curSize) {
  465.     if (curBuf != staticBuf) {
  466.         ckfree(curBuf);
  467.     }
  468.     curSize = length + 1;
  469.     curBuf = (char *) ckalloc((unsigned) curSize);
  470.     }
  471.  
  472.     /*
  473.      * Finally, concatenate the directory name with the remainder
  474.      * of the path in the buffer.
  475.      */
  476.  
  477.     strcpy(curBuf, dir);
  478.     strcat(curBuf, p);
  479.     if (fromPw) {
  480.     endpwent();
  481.     }
  482.     return curBuf;
  483. }
  484.  
  485. /*
  486.  *----------------------------------------------------------------------
  487.  *
  488.  * Tcl_GlobCmd --
  489.  *
  490.  *    This procedure is invoked to process the "glob" Tcl command.
  491.  *    See the user documentation for details on what it does.
  492.  *
  493.  * Results:
  494.  *    A standard Tcl result.
  495.  *
  496.  * Side effects:
  497.  *    See the user documentation.
  498.  *
  499.  *----------------------------------------------------------------------
  500.  */
  501.  
  502.     /* ARGSUSED */
  503. int
  504. Tcl_GlobCmd(dummy, interp, argc, argv)
  505.     ClientData dummy;            /* Not used. */
  506.     Tcl_Interp *interp;            /* Current interpreter. */
  507.     int argc;                /* Number of arguments. */
  508.     char **argv;            /* Argument strings. */
  509. {
  510.     int i, result, noComplain;
  511.  
  512.     if (argc < 2) {
  513.     notEnoughArgs:
  514.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  515.         " ?-nocomplain? name ?name ...?\"", (char *) NULL);
  516.     return TCL_ERROR;
  517.     }
  518.     noComplain = 0;
  519.     if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
  520.     if (argc < 3) {
  521.         goto notEnoughArgs;
  522.     }
  523.     noComplain = 1;
  524.     }
  525.  
  526.     for (i = 1 + noComplain; i < argc; i++) {
  527.     char *thisName;
  528.  
  529.     /*
  530.      * Do special checks for names starting at the root and for
  531.      * names beginning with ~.  Then let DoGlob do the rest.
  532.      */
  533.  
  534.     thisName = argv[i];
  535.     if (*thisName == '~') {
  536.         thisName = Tcl_TildeSubst(interp, thisName);
  537.         if (thisName == NULL) {
  538.         return TCL_ERROR;
  539.         }
  540.     }
  541.     if (*thisName == '/') {
  542.         result = DoGlob(interp, "/", thisName+1);
  543.     } else {
  544.         result = DoGlob(interp, "", thisName);
  545.     }
  546.     if (result != TCL_OK) {
  547.         return result;
  548.     }
  549.     }
  550.     if ((*interp->result == 0) && !noComplain) {
  551.     char *sep = "";
  552.  
  553.     Tcl_AppendResult(interp, "no files matched glob pattern",
  554.         (argc == 2) ? " \"" : "s \"", (char *) NULL);
  555.     for (i = 1; i < argc; i++) {
  556.         Tcl_AppendResult(interp, sep, argv[i], (char *) NULL);
  557.         sep = " ";
  558.     }
  559.     Tcl_AppendResult(interp, "\"", (char *) NULL);
  560.     return TCL_ERROR;
  561.     }
  562.     return TCL_OK;
  563. }
  564.